/*
 ============================================================================
 Name        : CPU.c
 Author      : James McQueen, Corwyn Simpson, Craig Markham
 Version     :
 Date        : 05/14/2012
 Description : TCSS-422 Operating Systems Scheduler
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "CPU.h"

/* Public functions */

CPU* cpu_init() {
	CPU* cpu = (CPU*) malloc(sizeof(CPU));

	cpu->my_scheduler = scheduler_init();
  cpu->clock_count = 0;
	cpu->my_pcb_list = (PCB**) malloc(MAX_PROCESSES * sizeof(PCB*));
  cpu->my_num_pcb = 0;
  cpu->my_tick_time.tv_sec = 0;
  cpu->my_tick_time.tv_nsec = DEFAULT_TICK_NS;
  int i = 0;
  for(; i < NUM_IODEVICES; i++)
  {
      cpu->my_interrupted_flags[i] = 0; //IRQs are down, not to be serviced
  }
  pthread_mutex_init(&cpu->my_interrupt_mutex, NULL);
	return cpu;
}

/**
 * Run the CPU.
 */
//starts the CPU, first process in ready queue set to run
//checks for interrupts to fix
void* cpu_run(void* cpu_pointer)
{
  CPU* cpu = (CPU*) cpu_pointer;

	PCB* curr_pcb;
	Request** my_request_ptr;
	int current_pc = 0;
	int my_request_size = 0;
	Request* my_request;

	//pull first process from ReadyQueue, set to run
	curr_pcb = (PCB*) ringbuff_get(cpu->my_scheduler->my_ready_queue);
	cpu->my_scheduler->my_running = curr_pcb;
	pcb_setState(cpu->my_scheduler->my_running, RUN);

	while(1){
		int i = 0;

		//Get the currently running pcb
		curr_pcb = cpu->my_scheduler->my_running;

		// check pcb pause/run status
		if(curr_pcb->my_state == RUN)
    {
//Step 1: Tick the PCB and the CPU
			pcb_incrementPC(curr_pcb);
      cpu->clock_count++;

//Step 2: Make requests if need be
			//Get the new step number (pc) of the current process
			current_pc = curr_pcb->PC;

			//Check if the current step matches a process request step num

			//get the current pcb's request array size
			my_request_size = cpu->my_scheduler->my_running->my_process_ptr->my_num_requests;

			//get the current pcb's request array
			my_request_ptr = cpu->my_scheduler->my_running->my_process_ptr->my_requests_ptr;
			for(i = 0; i < my_request_size; i++)
      {
				if(my_request_ptr[i]->time == current_pc)
        {
            my_request = my_request_ptr[i];
            switch(my_request->type)
            {
            case IO_REQUEST:
                scheduler_io_request(cpu->my_scheduler, my_request->enumeration);
                break;

            case MUTEX_ACQ:
                scheduler_mutex_acq(cpu->my_scheduler, my_request->enumeration);
                break;

            case MUTEX_GIVE:
                scheduler_mutex_give(cpu->my_scheduler, my_request->enumeration);
                break;

            case SHARED_WRITE:
                scheduler_mem_write(cpu->my_scheduler, my_request->enumeration);
                break;

            case SHARED_READ:
                scheduler_mem_read(cpu->my_scheduler, my_request->enumeration);
                break;
            }
        }
			}

			//handle current pcb request

//Step 3: Handle interrupts
      
      for(i = 0; i < NUM_IODEVICES; i++)
      {
          pthread_mutex_lock(&cpu->my_interrupt_mutex);
          if (cpu->my_interrupted_flags[i])
          {
              scheduler_interrupt(cpu->my_scheduler, i);
              cpu->my_interrupted_flags[i] = 0;
          }
          pthread_mutex_unlock(&cpu->my_interrupt_mutex);
      }
      
//Step 4: Sleep for one tick
      nanosleep(&cpu->my_tick_time, NULL);
		}
	}

    return (void*) 0; //successful execution
}


//places io device into interrupt queue
void cpu_interrupt(CPU* cpu, int device_num)
{
    pthread_mutex_lock(&cpu->my_interrupt_mutex);
    cpu->my_interrupted_flags[device_num] = 1;
    pthread_mutex_unlock(&cpu->my_interrupt_mutex);
}

//add process to CPU PCB List, Sched Ready Queue, and increment
void cpu_addProcess(CPU* cpu, PCB* pcb){
	ringbuff_put(cpu->my_scheduler->my_ready_queue, (void*)pcb);
  process_setStartTime(pcb_getProcess(pcb), cpu->clock_count);
	cpu->my_pcb_list[cpu->my_num_pcb] = pcb;
	cpu->my_num_pcb++;
}

//removes a process from the CPU and the Scheduler
void cpu_killProcess(CPU* cpu, int pid)
{
    //find where in the PCB list the Process is
    int pos = 0;
    while (pos < cpu->my_num_pcb && 
            pcb_getPID(cpu->my_pcb_list[pos]) == pid);
    {
        pos++;
    }
    if (pos < cpu->my_num_pcb)
    { // we found a process that matches
        PCB* killed = cpu->my_pcb_list[pos];
        //Set it's state to TERM
        pcb_setState(killed, TERM);
        // and remove it from the pcb_list, order doesn't matter
        cpu->my_pcb_list[pos] = cpu->my_pcb_list[cpu->my_num_pcb - 1];
        cpu->my_pcb_list[cpu->my_num_pcb - 1] = NULL;
        cpu->my_num_pcb--;
    } // Process will be removed from sched when it tries to run next
}

//returns the process list
PCB** cpu_getPCBList(CPU* cpu){
	return cpu->my_pcb_list;
}

//returns the number of processes running on the CPU
int cpu_getProcessCount(CPU* cpu){
	return cpu->my_num_pcb;
}


//gets a single Process from all the Processes in the system
PCB* cpu_getPCB(CPU* cpu, int process_id){
	PCB* pcb;
	int i;
	for(i = 0; i < cpu->my_num_pcb; i++){
		if(cpu->my_pcb_list[i]->PID == process_id)
			pcb = cpu->my_pcb_list[i];
	}
	return pcb;
}

//Gets the number of clock cycles the CPU has executed
int cpu_getClock(CPU* cpu){
	return cpu->clock_count;
}

//Pauses and resumes the CPU thread by setting the running Process to PAUSED
void cpu_toggleRunState(CPU* cpu){
	switch(pcb_getState(cpu->my_scheduler->my_running)){

	case PAUSED:
		pcb_setState(cpu->my_scheduler->my_running, RUN);
		break;
	case RUN:
		pcb_setState(cpu->my_scheduler->my_running, PAUSED);
		break;
	}
}
